package cn.jingyuan.bee.utils;

import cn.jingyuan.bee.utils.collection.CollectionUtils;
import cn.jingyuan.bee.utils.exceptions.UtilsException;
import cn.jingyuan.bee.utils.io.FileUtils;
import cn.jingyuan.bee.utils.io.IoUtils;
import cn.jingyuan.bee.utils.lang.Assert;
import cn.jingyuan.bee.utils.map.MapUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * XML 工具类<br>
 * 此工具使用 w3c dom 工具，不需要依赖第三方包。<br>
 * 工具类封装了 XML 文档的创建、读取、写出和部分 XML 操作
 */
public class XmlUtils {

    /** 在 XML 中无效的字符 正则 */
    public static final String INVALID_REGEX = "[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f]";

    /** XML 格式化输出默认缩进量 */
    public static final int INDENT_DEFAULT = 2;

    /** 默认的 DocumentBuilderFactory 实现 */
    private static String defaultDocumentBuilderFactory = "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl";

    /** 禁用默认的 DocumentBuilderFactory，禁用后如果有第三方的实现（如 oracle 的 xdb 包中的 xmlparse）将会自动加载实现 */
    public static synchronized void disableDefaultDocumentBuilderFactory() {
        defaultDocumentBuilderFactory = null;
    }

    /**
     * 读取解析 XML 文件
     *
     * @param file XML 文件
     *
     * @return XML 文档对象
     */
    public static Document readXML(File file) {
        Assert.notNull(file, "Xml file is null !");
        if (!file.exists()) {
            throw new UtilsException("File [{}] not a exist!", file.getAbsolutePath());
        }
        if (!file.isFile()) {
            throw new UtilsException("[{}] not a file!", file.getAbsolutePath());
        }

        try {
            file = file.getCanonicalFile();
        } catch (IOException e) {
            // ignore
        }

        BufferedInputStream in = null;
        try {
            in = FileUtils.getInputStream(file);
            return readXML(in);
        } finally {
            IoUtils.close(in);
        }
    }

    /**
     * 读取解析 XML 文件<br>
     * 如果给定内容以“&lt;”开头，表示这是一个 XML 内容，直接读取，否则按照路径处理<br>
     * 路径可以为相对路径，也可以是绝对路径，相对路径相对于 ClassPath
     *
     * @param pathOrContent 内容或路径
     *
     * @return XML 文档对象
     */
    public static Document readXML(String pathOrContent) {
        if (StringUtils.startWith(pathOrContent, '<')) {
            return parseXml(pathOrContent);
        }
        return readXML(FileUtils.file(pathOrContent));
    }

    /**
     * 读取解析 XML 文件<br>
     * 编码在 XML 中定义
     *
     * @param inputStream XML 流
     *
     * @return XML 文档对象
     *
     * @throws UtilsException IO 异常或转换异常
     */
    public static Document readXML(InputStream inputStream) throws UtilsException {
        return readXML(new InputSource(inputStream));
    }

    /**
     * 读取解析 XML 文件
     *
     * @param reader XML 流
     *
     * @return XML 文档对象
     *
     * @throws UtilsException IO 异常或转换异常
     */
    public static Document readXML(Reader reader) throws UtilsException {
        return readXML(new InputSource(reader));
    }

    /**
     * 读取解析 XML 文件<br>
     * 编码在 XML 中定义
     *
     * @param source {@link InputSource}
     *
     * @return XML 文档对象
     */
    public static Document readXML(InputSource source) {
        final DocumentBuilder builder = createDocumentBuilder();
        try {
            return builder.parse(source);
        } catch (Exception e) {
            throw new UtilsException(e, "Parse XML from stream error!");
        }
    }

    /**
     * 将 String 类型的 XML 转换为 XML 文档
     *
     * @param xmlString XML 字符串
     *
     * @return XML 文档
     */
    public static Document parseXml(String xmlString) {
        if (StringUtils.isBlank(xmlString)) {
            throw new IllegalArgumentException("XML content string is empty !");
        }
        xmlString = cleanInvalid(xmlString);
        return readXML(new InputSource(StringUtils.getReader(xmlString)));
    }

    /**
     * 从 XML 中读取对象 Reads serialized object from the XML file.
     *
     * @param <T> 对象类型
     * @param source XML 文件
     *
     * @return 对象
     */
    public static <T> T readObjectFromXml(File source) {
        return readObjectFromXml(new InputSource(FileUtils.getInputStream(source)));
    }

    /**
     * 从 XML 中读取对象 Reads serialized object from the XML file.
     *
     * @param <T> 对象类型
     * @param xmlString XML 内容
     *
     * @return 对象
     */
    public static <T> T readObjectFromXml(String xmlString) {
        return readObjectFromXml(new InputSource(StringUtils.getReader(xmlString)));
    }

    /**
     * 从 XML 中读取对象 Reads serialized object from the XML file.
     *
     * @param <T> 对象类型
     * @param source {@link InputSource}
     *
     * @return 对象
     */

    public static <T> T readObjectFromXml(InputSource source) {
        Object result;
        XMLDecoder xmldec = null;
        try {
            xmldec = new XMLDecoder(source);
            result = xmldec.readObject();
        } finally {
            IoUtils.close(xmldec);
        }
        return (T) result;
    }

    /**
     * 将 XML 文档转换为 String<br>
     * 字符编码使用 XML 文档中的编码，获取不到则使用 UTF-8<br>
     * 默认非格式化输出，若想格式化请使用 {@link #format(Document)}
     *
     * @param doc XML 文档
     *
     * @return XML 字符串
     */
    public static String toString(Document doc) {
        return toString(doc, false);
    }

    /**
     * 将 XML 文档转换为 String<br>
     * 字符编码使用 XML 文档中的编码，获取不到则使用 UTF-8
     *
     * @param doc XML 文档
     * @param isPretty 是否格式化输出
     *
     * @return XML 字符串
     */
    public static String toString(Document doc, boolean isPretty) {
        return toString(doc, CharsetUtils.UTF_8, isPretty);
    }

    /**
     * 将 XML 文档转换为 String<br>
     * 字符编码使用 XML 文档中的编码，获取不到则使用 UTF-8
     *
     * @param doc XML 文档
     * @param charset 编码
     * @param isPretty 是否格式化输出
     *
     * @return XML 字符串
     */
    public static String toString(Document doc, String charset, boolean isPretty) {
        final StringWriter writer = StringUtils.getWriter();
        try {
            write(doc, writer, charset, isPretty ? INDENT_DEFAULT : 0);
        } catch (Exception e) {
            throw new UtilsException(e, "Trans xml document to string error!");
        }
        return writer.toString();
    }

    /**
     * 格式化 XML 输出
     *
     * @param doc {@link Document} XML 文档
     *
     * @return 格式化后的 XML 字符串
     */
    public static String format(Document doc) {
        return toString(doc, true);
    }

    /**
     * 格式化 XML 输出
     *
     * @param xmlString XML 字符串
     *
     * @return 格式化后的 XML 字符串
     */
    public static String format(String xmlString) {
        return format(parseXml(xmlString));
    }

    /**
     * 将 XML 文档写入到文件<br>
     * 使用 Document 中的编码
     *
     * @param doc XML 文档
     * @param absolutePath 文件绝对路径，不存在会自动创建
     */
    public static void toFile(Document doc, String absolutePath) {
        toFile(doc, absolutePath, null);
    }

    /**
     * 将 XML 文档写入到文件<br>
     *
     * @param doc XML 文档
     * @param path 文件路径绝对路径或相对 ClassPath 路径，不存在会自动创建
     * @param charset 自定义 XML 文件的编码，如果为 {@code null} 读取 XML 文档中的编码，否则默认 UTF-8
     */
    public static void toFile(Document doc, String path, String charset) {
        if (StringUtils.isBlank(charset)) {
            charset = doc.getXmlEncoding();
        }
        if (StringUtils.isBlank(charset)) {
            charset = CharsetUtils.UTF_8;
        }

        BufferedWriter writer = null;
        try {
            writer = FileUtils.getWriter(path, charset, false);
            write(doc, writer, charset, INDENT_DEFAULT);
        } finally {
            IoUtils.close(writer);
        }
    }

    /**
     * 将 XML 文档写出
     *
     * @param node {@link Node} XML 文档节点或文档本身
     * @param writer 写出的 Writer，Writer 决定了输出 XML 的编码
     * @param charset 编码
     * @param indent 格式化输出中缩进量，小于 1 表示不格式化输出
     */
    public static void write(Node node, Writer writer, String charset, int indent) {
        transform(new DOMSource(node), new StreamResult(writer), charset, indent);
    }

    /**
     * 将 XML 文档写出
     *
     * @param node {@link Node} XML 文档节点或文档本身
     * @param out 写出的 Writer，Writer 决定了输出 XML 的编码
     * @param charset 编码
     * @param indent 格式化输出中缩进量，小于 1 表示不格式化输出
     */
    public static void write(Node node, OutputStream out, String charset, int indent) {
        transform(new DOMSource(node), new StreamResult(out), charset, indent);
    }

    /**
     * 将 XML 文档写出<br>
     * 格式化输出逻辑参考：https://stackoverflow.com/questions/139076/how-to-pretty-print-xml-from-java
     *
     * @param source 源
     * @param result 目标
     * @param charset 编码
     * @param indent 格式化输出中缩进量，小于 1 表示不格式化输出
     */
    public static void transform(Source source, Result result, String charset, int indent) {
        final TransformerFactory factory = TransformerFactory.newInstance();
        try {
            final Transformer xFormer = factory.newTransformer();
            if (indent > 0) {
                xFormer.setOutputProperty(OutputKeys.INDENT, "yes");
                xFormer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", String.valueOf(indent));
            }
            if (StringUtils.isNotBlank(charset)) {
                xFormer.setOutputProperty(OutputKeys.ENCODING, charset);
            }
            xFormer.transform(source, result);
        } catch (Exception e) {
            throw new UtilsException(e, "Trans xml document to string error!");
        }
    }

    /**
     * 创建 XML 文档<br>
     * 创建的 XML 默认是 utf8 编码，修改编码的过程是在 toStr 和 toFile 方法里，即 XML 在转为文本的时候才定义编码
     *
     * @return XML 文档
     */
    public static Document createXml() {
        return createDocumentBuilder().newDocument();
    }

    /**
     * 创建 DocumentBuilder
     *
     * @return DocumentBuilder
     */
    public static DocumentBuilder createDocumentBuilder() {
        DocumentBuilder builder;
        try {
            builder = createDocumentBuilderFactory().newDocumentBuilder();
        } catch (Exception e) {
            throw new UtilsException(e, "Create xml document error!");
        }
        return builder;
    }

    /**
     * 创建 {@link DocumentBuilderFactory}
     * <p>
     * 默认使用"com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl"<br>
     * 如果使用第三方实现，请调用 {@link #disableDefaultDocumentBuilderFactory()}
     * </p>
     *
     * @return {@link DocumentBuilderFactory}
     */
    public static DocumentBuilderFactory createDocumentBuilderFactory() {
        final DocumentBuilderFactory factory;
        if (StringUtils.isNotEmpty(defaultDocumentBuilderFactory)) {
            factory = DocumentBuilderFactory.newInstance(defaultDocumentBuilderFactory, null);
        } else {
            factory = DocumentBuilderFactory.newInstance();
        }
        return disableXXE(factory);
    }

    /**
     * 创建 XML 文档<br>
     * 创建的 XML 默认是 utf8 编码，修改编码的过程是在 toStr 和 toFile 方法里，即 XML 在转为文本的时候才定义编码
     *
     * @param rootElementName 根节点名称
     *
     * @return XML 文档
     */
    public static Document createXml(String rootElementName) {
        return createXml(rootElementName, null);
    }

    /**
     * 创建 XML 文档<br>
     * 创建的 XML 默认是 utf8 编码，修改编码的过程是在 toStr 和 toFile 方法里，即 XML 在转为文本的时候才定义编码
     *
     * @param rootElementName 根节点名称
     * @param namespace 命名空间，无则传 null
     *
     * @return XML 文档
     */
    public static Document createXml(String rootElementName, String namespace) {
        final Document doc = createXml();
        doc.appendChild(null == namespace ? doc.createElement(rootElementName) : doc.createElementNS(rootElementName, namespace));
        return doc;
    }

    /**
     * 获得 XML 文档根节点
     *
     * @param doc {@link Document}
     *
     * @return 根节点
     *
     * @see Document#getDocumentElement()
     */
    public static Element getRootElement(Document doc) {
        return (null == doc) ? null : doc.getDocumentElement();
    }

    /**
     * 去除 XML 文本中的无效字符
     *
     * @param xmlContent XML 文本
     *
     * @return 当传入为 null 时返回 null
     */
    public static String cleanInvalid(String xmlContent) {
        if (xmlContent == null) {
            return null;
        }
        return xmlContent.replaceAll(INVALID_REGEX, "");
    }

    /**
     * 根据节点名获得子节点列表
     *
     * @param element 节点
     * @param tagName 节点名，如果节点名为空（null 或 blank），返回所有子节点
     *
     * @return 节点列表
     */
    public static List<Element> getElements(Element element, String tagName) {
        final NodeList nodeList = StringUtils.isBlank(tagName) ? element.getChildNodes() : element.getElementsByTagName(tagName);
        return transElements(element, nodeList);
    }

    /**
     * 根据节点名获得第一个子节点
     *
     * @param element 节点
     * @param tagName 节点名
     *
     * @return 节点
     */
    public static Element getElement(Element element, String tagName) {
        final NodeList nodeList = element.getElementsByTagName(tagName);
        if (nodeList == null || nodeList.getLength() < 1) {
            return null;
        }
        int length = nodeList.getLength();
        for (int i = 0; i < length; i++) {
            Element childEle = (Element) nodeList.item(i);
            if (childEle == null || childEle.getParentNode() == element) {
                return childEle;
            }
        }
        return null;
    }

    /**
     * 根据节点名获得第一个子节点
     *
     * @param element 节点
     * @param tagName 节点名
     *
     * @return 节点中的值
     */
    public static String elementText(Element element, String tagName) {
        Element child = getElement(element, tagName);
        return child == null ? null : child.getTextContent();
    }

    /**
     * 根据节点名获得第一个子节点
     *
     * @param element 节点
     * @param tagName 节点名
     * @param defaultValue 默认值
     *
     * @return 节点中的值
     */
    public static String elementText(Element element, String tagName, String defaultValue) {
        Element child = getElement(element, tagName);
        return child == null ? defaultValue : child.getTextContent();
    }

    /**
     * 将 NodeList 转换为 Element 列表
     *
     * @param nodeList NodeList
     *
     * @return Element 列表
     */
    public static List<Element> transElements(NodeList nodeList) {
        return transElements(null, nodeList);
    }

    /**
     * 将 NodeList 转换为 Element 列表<br>
     * 非 Element 节点将被忽略
     *
     * @param parentEle 父节点，如果指定将返回此节点的所有直接子节点，null 返回所有就节点
     * @param nodeList NodeList
     *
     * @return Element 列表
     */
    public static List<Element> transElements(Element parentEle, NodeList nodeList) {
        int length = nodeList.getLength();
        final ArrayList<Element> elements = new ArrayList<>(length);
        Node node;
        Element element;
        for (int i = 0; i < length; i++) {
            node = nodeList.item(i);
            if (Node.ELEMENT_NODE == node.getNodeType()) {
                element = (Element) nodeList.item(i);
                if (parentEle == null || element.getParentNode() == parentEle) {
                    elements.add(element);
                }
            }
        }

        return elements;
    }

    /**
     * 将可序列化的对象转换为 XML 写入文件，已经存在的文件将被覆盖<br>
     * Writes serializable object to a XML file. Existing file will be overwritten
     *
     * @param dest 目标文件
     * @param bean 对象
     */
    public static void writeObjectAsXml(File dest, Object bean) {
        XMLEncoder xmlenc = null;
        try {
            xmlenc = new XMLEncoder(FileUtils.getOutputStream(dest));
            xmlenc.writeObject(bean);
        } finally {
            // 关闭 XMLEncoder 会相应关闭 OutputStream
            IoUtils.close(xmlenc);
        }
    }

    /**
     * 创建 XPath<br>
     * Xpath 相关文章：https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html
     *
     * @return {@link XPath}
     */
    public static XPath createXPath() {
        return XPathFactory.newInstance().newXPath();
    }

    /**
     * 通过 XPath 方式读取 XML 节点等信息<br>
     * Xpath 相关文章：https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html
     *
     * @param expression XPath 表达式
     * @param source 资源，可以是 Docunent、Node 节点等
     *
     * @return 匹配返回类型的值
     */
    public static Element getElementByXPath(String expression, Object source) {
        return (Element) getNodeByXPath(expression, source);
    }

    /**
     * 通过 XPath 方式读取 XML 的 NodeList<br>
     * Xpath 相关文章：https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html
     *
     * @param expression XPath 表达式
     * @param source 资源，可以是 Docunent、Node 节点等
     *
     * @return NodeList
     */
    public static NodeList getNodeListByXPath(String expression, Object source) {
        return (NodeList) getByXPath(expression, source, XPathConstants.NODESET);
    }

    /**
     * 通过 XPath 方式读取 XML 节点等信息<br>
     * Xpath 相关文章：https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html
     *
     * @param expression XPath 表达式
     * @param source 资源，可以是 Docunent、Node 节点等
     *
     * @return 匹配返回类型的值
     */
    public static Node getNodeByXPath(String expression, Object source) {
        return (Node) getByXPath(expression, source, XPathConstants.NODE);
    }

    /**
     * 通过 XPath 方式读取 XML 节点等信息<br>
     * Xpath 相关文章：https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html
     *
     * @param expression XPath 表达式
     * @param source 资源，可以是 Docunent、Node 节点等
     * @param returnType 返回类型，{@link XPathConstants}
     *
     * @return 匹配返回类型的值
     */
    public static Object getByXPath(String expression, Object source, QName returnType) {
        final XPath xPath = createXPath();
        try {
            if (source instanceof InputSource) {
                return xPath.evaluate(expression, (InputSource) source, returnType);
            } else {
                return xPath.evaluate(expression, source, returnType);
            }
        } catch (XPathExpressionException e) {
            throw new UtilsException(e);
        }
    }

    /**
     * 转义 XML 特殊字符:
     *
     * <pre>
     * &amp; (ampersand) 替换为 &amp;amp;
     * &lt; (小于) 替换为 &amp;lt;
     * &gt; (大于) 替换为 &amp;gt;
     * &quot; (双引号) 替换为 &amp;quot;
     * </pre>
     *
     * @param string 被替换的字符串
     *
     * @return 替换后的字符串
     */
    public static String escape(String string) {
        return EscapeUtils.escape(string);
    }

    /**
     * 反转义 XML 特殊字符:
     *
     * @param string 被替换的字符串
     *
     * @return 替换后的字符串
     *
     * @see EscapeUtils#unescape(String)
     */
    public static String unescape(String string) {
        return EscapeUtils.unescape(string);
    }

    /**
     * XML 格式字符串转换为 Map
     *
     * @param xmlString XML 字符串
     *
     * @return XML 数据转换后的 Map
     */
    public static Map<String, Object> xmlToMap(String xmlString) {
        return xmlToMap(xmlString, new HashMap<>());
    }

    /**
     * XML 格式字符串转换为 Map
     *
     * @param node XML 节点
     *
     * @return XML 数据转换后的 Map
     */
    public static Map<String, Object> xmlToMap(Node node) {
        return xmlToMap(node, new HashMap<>());
    }

    /**
     * XML 格式字符串转换为 Map<br>
     * 只支持第一级别的 XML，不支持多级 XML
     *
     * @param xmlStr XML 字符串
     * @param result 结果 Map 类型
     *
     * @return XML 数据转换后的 Map
     */
    public static Map<String, Object> xmlToMap(String xmlStr, Map<String, Object> result) {
        final Document doc = parseXml(xmlStr);
        final Element root = getRootElement(doc);
        root.normalize();

        return xmlToMap(root, result);
    }

    /**
     * XML 节点转换为 Map
     *
     * @param node XML 节点
     * @param result 结果 Map 类型
     *
     * @return XML 数据转换后的 Map
     */
    public static Map<String, Object> xmlToMap(Node node, Map<String, Object> result) {
        if (null == result) {
            result = new HashMap<>();
        }
        final NodeList nodeList = node.getChildNodes();
        final int length = nodeList.getLength();
        Node childNode;
        Element childEle;
        for (int i = 0; i < length; ++i) {
            childNode = nodeList.item(i);
            if (!isElement(childNode)) {
                continue;
            }

            childEle = (Element) childNode;
            final Object value = result.get(childEle.getNodeName());
            Object newValue = null;
            if (childEle.hasChildNodes()) {
                // 子节点继续递归遍历
                final Map<String, Object> map = xmlToMap(childEle);
                if (MapUtils.isNotEmpty(map)) {
                    newValue = map;
                } else {
                    newValue = childEle.getTextContent();
                }
            } else {
                newValue = childEle.getTextContent();
            }


            if (null != newValue) {
                if (null != value) {
                    if (value instanceof List) {
                        ((List<Object>) value).add(newValue);
                    } else {
                        result.put(childEle.getNodeName(), CollectionUtils.newArrayList(value, newValue));
                    }
                } else {
                    result.put(childEle.getNodeName(), newValue);
                }
            }
        }
        return result;
    }

    /**
     * 将 Map 转换为 XML 格式的字符串
     *
     * @param data Map 类型数据
     * @param rootName 根节点名
     *
     * @return XML 格式的字符串
     */
    public static String mapToXmlStr(Map<?, ?> data, String rootName) {
        return toString(mapToXml(data, rootName));
    }

    /**
     * 将 Map 转换为 XML 格式的字符串
     *
     * @param data Map 类型数据
     * @param rootName 根节点名
     * @param namespace 命名空间，可以为 null
     *
     * @return XML 格式的字符串
     */
    public static String mapToXmlStr(Map<?, ?> data, String rootName, String namespace) {
        return toString(mapToXml(data, rootName, namespace));
    }

    /**
     * 将 Map 转换为 XML
     *
     * @param data Map 类型数据
     * @param rootName 根节点名
     *
     * @return XML
     */
    public static Document mapToXml(Map<?, ?> data, String rootName) {

        return mapToXml(data, rootName, null);
    }

    /**
     * 将 Map 转换为 XML
     *
     * @param data Map 类型数据
     * @param rootName 根节点名
     * @param namespace 命名空间，可以为 null
     *
     * @return XML
     */
    public static Document mapToXml(Map<?, ?> data, String rootName, String namespace) {
        final Document doc = createXml();
        final Element root = appendChild(doc, rootName, namespace);

        mapToXml(doc, root, data);
        return doc;
    }

    /**
     * 给定节点是否为 {@link Element} 类型节点
     *
     * @param node 节点
     *
     * @return 是否为 {@link Element} 类型节点
     */
    public static boolean isElement(Node node) {
        return (null != node) && Node.ELEMENT_NODE == node.getNodeType();
    }

    /**
     * 在已有节点上创建子节点
     *
     * @param node 节点
     * @param tagName 标签名
     *
     * @return 子节点
     */
    public static Element appendChild(Node node, String tagName) {
        return appendChild(node, tagName, null);
    }

    /**
     * 在已有节点上创建子节点
     *
     * @param node 节点
     * @param tagName 标签名
     * @param namespace 命名空间，无传 null
     *
     * @return 子节点
     */
    public static Element appendChild(Node node, String tagName, String namespace) {
        final Document doc = (node instanceof Document) ? (Document) node : node.getOwnerDocument();
        final Element child = (null == namespace) ? doc.createElement(tagName) : doc.createElementNS(namespace, tagName);
        node.appendChild(child);
        return child;
    }

    /**
     * 将 Map 转换为 XML 格式的字符串
     *
     * @param doc {@link Document}
     * @param element 节点
     * @param data Map 类型数据
     */
    private static void mapToXml(Document doc, Element element, Map<?, ?> data) {
        Element filedEle;
        Object key;
        for (Entry<?, ?> entry : data.entrySet()) {
            key = entry.getKey();
            if (null == key) {
                continue;
            }
            // key 作为标签名，无值的节点作为空节点创建
            filedEle = doc.createElement(key.toString());
            element.appendChild(filedEle);
            // value 作为标签内的值。
            final Object value = entry.getValue();
            if (null == value) {
                continue;
            }
            if (value instanceof List) {
                for (Object listEle : (List) value) {
                    if (listEle instanceof Map) {
                        // 如果值依旧为 map，递归继续
                        mapToXml(doc, filedEle, (Map<?, ?>) listEle);
                    } else {
                        // 创建文本节点
                        filedEle.appendChild(doc.createTextNode(value.toString()));
                    }
                }
            } else if (value instanceof Map) {
                // 如果值依旧为 map，递归继续
                mapToXml(doc, filedEle, (Map<?, ?>) value);
            } else {
                filedEle.appendChild(doc.createTextNode(value.toString()));

            }
        }
    }

    /**
     * 关闭 XXE，避免漏洞攻击<br>
     * see: https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#JAXP_DocumentBuilderFactory.2C_SAXParserFactory_and_DOM4J
     *
     * @param dbf DocumentBuilderFactory
     *
     * @return DocumentBuilderFactory
     */
    private static DocumentBuilderFactory disableXXE(DocumentBuilderFactory dbf) {
        String feature;
        try {
            // This is the PRIMARY defense. If DTDs (doctypes) are disallowed, almost all XML entity attacks are prevented
            // Xerces 2 only - http://xerces.apache.org/xerces2-j/features.html#disallow-doctype-decl
            feature = "http://apache.org/xml/features/disallow-doctype-decl";
            dbf.setFeature(feature, true);
            // If you can't completely disable DTDs, then at least do the following:
            // Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-general-entities
            // Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-general-entities
            // JDK7+ - http://xml.org/sax/features/external-general-entities
            feature = "http://xml.org/sax/features/external-general-entities";
            dbf.setFeature(feature, false);
            // Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-parameter-entities
            // Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-parameter-entities
            // JDK7+ - http://xml.org/sax/features/external-parameter-entities
            feature = "http://xml.org/sax/features/external-parameter-entities";
            dbf.setFeature(feature, false);
            // Disable external DTDs as well
            feature = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
            dbf.setFeature(feature, false);
            // and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
            dbf.setXIncludeAware(false);
            dbf.setExpandEntityReferences(false);
        } catch (ParserConfigurationException e) {
            // ignore
        }
        return dbf;
    }

}
